/*
 *      logviewer.cpp
 *
 *      Copyright 2009 David Vachulka <david@konstrukce-cad.com>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

#include "logviewer.h"
#include "utils.h"
#include "icons.h"
#include "config.h"
#include "i18n.h"
#include <string>
#include <fstream>
#include <FX88591Codec.h>
#include <FXUTF16Codec.h>
#include "dxirc.h"
#include "dxtext.h"

#define preferences dxirc::instance()->getPreferences()

static FXbool isBadchar(FXchar c)
{
    switch(c) {
        case ' ':
        case ',':
        case '\0':
        case '\02':
        case '\03':
        case '\017':
        case '\021':
        case '\026':
        case '\035':
        case '\037':
        case '\n':
        case '\r':
        case '<':
        case '>':
        case '"':
        case '\'':
            return TRUE;
        default:
            return FALSE;
    }
}

// Sort ascending order, case insensitive
static FXint ascendingCaseLogItem(const FXTreeItem *a, const FXTreeItem *b)
{
    return comparecase(((LogItem*)a)->isMonth()?utils::getMonthNumber(a->getText()):a->getText(), ((LogItem*)b)->isMonth()?utils::getMonthNumber(b->getText()):b->getText());
}

FXIMPLEMENT(LogItem, FXTreeItem, NULL, 0)

FXDEFMAP(LogViewer) LogViewerMap[] = {
    FXMAPFUNC(SEL_COMMAND,              LogViewer_CLOSE,                LogViewer::onClose),
    FXMAPFUNC(SEL_CLOSE,                0,                              LogViewer::onClose),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_TREE,                 LogViewer::onTree),
    FXMAPFUNC(SEL_EXPANDED,             LogViewer_TREE,                 LogViewer::onTree),
    FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,   LogViewer_TREE,                 LogViewer::onRightTree),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_RESET,                LogViewer::onReset),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_SEARCH,               LogViewer::onSearch),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_SEARCHNEXT,           LogViewer::onSearchNext),
    FXMAPFUNCS(SEL_COMMAND,             LogViewer_ALL, LogViewer_FILE,  LogViewer::onCmdSearchOptions),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_PACK,                 LogViewer::onPack),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_UNPACK,               LogViewer::onUnpack),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_DELETEITEM,           LogViewer::onDelete),
    FXMAPFUNC(SEL_TEXTLINK,             LogViewer_TEXT,                 LogViewer::onTextLink),
    FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,   LogViewer_TEXT,                 LogViewer::onTextRightMouse),
    FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,  LogViewer_TEXT,                 LogViewer::onTextMiddleMouse),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_OPENLINK,             LogViewer::onOpenLink),
    FXMAPFUNC(SEL_COMMAND,              LogViewer_COPYLINK,             LogViewer::onCopyLink),
    FXMAPFUNC(SEL_CLIPBOARD_REQUEST,    0,                              LogViewer::onClipboardRequest),
    FXMAPFUNC(SEL_TIMEOUT,              LogViewer_STIMEOUT,             LogViewer::onStatusTimeout),
    FXMAPFUNC(SEL_KEYPRESS, 0,                                          LogViewer::onKeyPress)
};

FXIMPLEMENT(LogViewer, FXTopWindow, LogViewerMap, ARRAYNUMBER(LogViewerMap))

LogViewer::LogViewer(FXApp *app, const FXString &lpath, FXFont *fnt)
        : FXTopWindow(app, _("dxirc - log viewer"), NULL, NULL, DECOR_ALL, 0,0,800,500, 0,0,0,0, 0,0), m_logPath(lpath)
{
    setIcon(ICO_BIG);
    setMiniIcon(ICO_SMALL);

    m_all = TRUE;
    m_network = FALSE;
    m_channel = FALSE;
    m_file = FALSE;
    m_icase = FALSE;
    m_treeLoaded = FALSE;
    m_targetAll.connect(m_all);
    m_targetAll.setTarget(this);
    m_targetAll.setSelector(LogViewer_ALL);
    m_targetNetwork.connect(m_network);
    m_targetNetwork.setTarget(this);
    m_targetNetwork.setSelector(LogViewer_NETWORK);
    m_targetChannel.connect(m_channel);
    m_targetChannel.setTarget(this);
    m_targetChannel.setSelector(LogViewer_CHANNEL);
    m_targetFile.connect(m_file);
    m_targetFile.setTarget(this);
    m_targetFile.setSelector(LogViewer_FILE);
    m_targetIcase.connect(m_icase);

    m_statusbar = new FXHorizontalFrame(this, LAYOUT_SIDE_BOTTOM|JUSTIFY_LEFT|LAYOUT_FILL_X|FRAME_NONE, 0,0,0,0, 1,1,1,1);
    m_statuslabel = new FXLabel(m_statusbar, " ", NULL, FRAME_SUNKEN|LAYOUT_LEFT|JUSTIFY_LEFT|LAYOUT_FILL_X);
    m_statusbar->hide();

    m_buttonframe = new FXHorizontalFrame(this, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X);
    m_buttonClose = new dxEXButton(m_buttonframe, _("C&lose"), NULL, this, LogViewer_CLOSE, BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X, 0,0,0,0, 10,10,2,2);

    m_content = new FXHorizontalFrame(this,  LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y);

    m_splitter = new FXSplitter(m_content, LAYOUT_FILL_X | LAYOUT_FILL_Y | SPLITTER_REVERSED | SPLITTER_TRACKING);
    m_textframe = new FXVerticalFrame(m_splitter, FRAME_SUNKEN | FRAME_THICK | LAYOUT_FILL_X | LAYOUT_FILL_Y);
    m_text = new dxText(m_textframe, this, LogViewer_TEXT, FRAME_SUNKEN | LAYOUT_FILL_X | LAYOUT_FILL_Y);
    m_text->setFont(fnt);
    m_text->setTextColor(preferences.m_colors.text);
    m_text->setBackColor(preferences.m_colors.back);
    m_listframe = new FXVerticalFrame(m_splitter, FRAME_SUNKEN | FRAME_THICK | LAYOUT_FILL_X | LAYOUT_FILL_Y | LAYOUT_FIX_WIDTH);
    m_searchframe = new FXHorizontalFrame(m_listframe, FRAME_THICK | LAYOUT_FILL_X);
    m_searchfield = new FXTextField(m_searchframe, 15, this, LogViewer_SEARCH, TEXTFIELD_ENTER_ONLY|FRAME_THICK|FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y);
    m_searchfield->setTipText(_("F3 for next"));
    m_searchfield->disable();
    m_buttonSearch = new dxEXButton(m_searchframe, _("&Search"), NULL, this, LogViewer_SEARCH, BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X, 0,0,0,0, 10,10,2,2);
    m_buttonSearch->disable();
    m_buttonReset = new dxEXButton(m_searchframe, _("&Reset"), NULL, this, LogViewer_RESET, BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X, 0,0,0,0, 10,10,2,2);
    m_buttonReset->disable();
    m_group = new FXGroupBox(m_listframe, _("Search options"), LAYOUT_SIDE_TOP|FRAME_GROOVE|LAYOUT_FILL_X, 0,0,0,0);
    m_buttonIcase = new FXCheckButton(m_group, _("&Ignore case"), &m_targetIcase, FXDataTarget::ID_VALUE);
    m_buttonIcase->disable();
    m_buttonFile = new FXRadioButton(m_group, _("Search file"), &m_targetFile, FXDataTarget::ID_VALUE);
    m_buttonFile->disable();
    m_buttonChannel = new FXRadioButton(m_group, _("Search channel/query"), &m_targetChannel, FXDataTarget::ID_VALUE);
    m_buttonChannel->disable();
    m_buttonNetwork = new FXRadioButton(m_group, _("Search network"), &m_targetNetwork, FXDataTarget::ID_VALUE);
    m_buttonNetwork->disable();
    m_buttonAll = new FXRadioButton(m_group, _("Search all"), &m_targetAll, FXDataTarget::ID_VALUE);
    m_buttonAll->disable();
    m_treeframe = new FXVerticalFrame(m_listframe, FRAME_THICK | LAYOUT_FILL_X | LAYOUT_FILL_Y);
    m_treeHistory = new FXTreeList(m_treeframe, this, LogViewer_TREE, TREELIST_SHOWS_BOXES | TREELIST_SHOWS_LINES | FRAME_SUNKEN | LAYOUT_FILL_X | LAYOUT_FILL_Y);
    m_treeHistory->setSortFunc(ascendingCaseLogItem);
    m_treeHistory->setScrollStyle(HSCROLLING_OFF);
    getAccelTable()->addAccel(MKUINT(KEY_F3, 0), this, FXSEL(SEL_COMMAND,LogViewer_SEARCHNEXT));

    m_searchstring = "";
    m_itemOnRight = NULL;
    m_link = "";
}

LogViewer::~LogViewer()
{
}

void LogViewer::show()
{
    FXShell::show();
    raise();
    loadTree();
    m_searchfield->disable();
    m_buttonSearch->disable();
    m_buttonReset->disable();
    m_buttonIcase->disable();
    m_buttonFile->disable();
    m_buttonChannel->disable();
    m_buttonNetwork->disable();
    m_buttonAll->disable();
}

void LogViewer::show(FXuint placement)
{
    place(placement);
    FXShell::show();
    raise();
    loadTree();
    m_searchfield->disable();
    m_buttonSearch->disable();
    m_buttonReset->disable();
    m_buttonIcase->disable();
    m_buttonFile->disable();
    m_buttonChannel->disable();
    m_buttonNetwork->disable();
    m_buttonAll->disable();
}

void LogViewer::setLogPath(const FXString &path)
{
    if(m_logPath != path)
        m_logPath = path;
}

void LogViewer::setFont(FXFont *fnt)
{
    m_text->setFont(fnt);
}

long LogViewer::onClose(FXObject*, FXSelector, void*)
{
    m_treeHistory->clearItems(TRUE);
    m_text->clearText();
    m_searchfield->setText("");
    m_searchstring.clear();
    hide();
    return 1;
}

long LogViewer::onSearch(FXObject*, FXSelector, void*)
{
    m_text->clearSelection();
    enableAllItems();
    if(m_searchfield->getText().empty())
        return 1;
    else
    {
        m_searchstring = m_searchfield->getText();
        m_searchfield->setText("");
    }
    updateStatus(_("Search started..."));
    if(m_file)
    {
        searchInLogFile();
        updateStatus(_("Search finished"));
        return 1;
    }
    else if(m_channel)
    {
        searchInChannel(getChannelItem((LogItem*)m_treeHistory->getCurrentItem()));
        updateStatus(_("Search finished"));
        return 1;
    }
    else if(m_network)
    {
        searchInNetwork(getNetworkItem((LogItem*)m_treeHistory->getCurrentItem()));
        updateStatus(_("Search finished"));
        return 1;
    }
    else //search over all files
    {
        searchInAll();
        updateStatus(_("Search finished"));
        return 1;
    }
    return 0;
}

long LogViewer::onSearchNext(FXObject*, FXSelector, void*)
{
    FXint index[10];
    FXint beg[10];
    FXint end[10];
    FXint start, pos;
    if(!m_searchstring.empty())
    {
        FXuint flags = SEARCH_FORWARD|SEARCH_WRAP;
        if(m_icase) flags |= SEARCH_IGNORECASE;
        m_text->findStart(flags, start, pos);
        if(m_text->findText(m_searchstring,index,beg,end,start,pos,flags|SEARCH_WRAP,10))
        {
            m_text->setSelection(index[0], beg[0], index[0], end[0]-1);
            m_text->makePositionVisible(index[0], beg[0]);
        }
        else
        {
            getApp()->beep();
        }
    }
    return 1;
}

long LogViewer::onReset(FXObject*, FXSelector, void*)
{
    m_searchstring.clear();
    m_text->clearSelection();
    m_searchfield->setFocus();
    enableAllItems();
    m_buttonReset->disable();
    return 1;
}

long LogViewer::onTree(FXObject*, FXSelector, void *ptr)
{
    LogItem *item = (LogItem*)ptr;
    if(item->isLogFile())
    {
        if(!loadFile(getItemPathname(item)))
        {
            LogItem *parent = (LogItem*)item->getParent();
            m_treeHistory->setCurrentItem(parent, TRUE);
            scan();
            m_text->clearText();
            m_buttonIcase->enable();
            m_buttonFile->disable();
            m_file = FALSE;
            if(parent->isMonth())
            {
                m_buttonChannel->enable();
                m_channel = TRUE;
                m_network = FALSE;
                m_all = FALSE;
            }
            else
            {
                m_buttonChannel->disable();
                m_channel = FALSE;
                m_network = TRUE;
                m_all = FALSE;
            }
            m_buttonNetwork->enable();
            m_buttonAll->enable();
            m_searchfield->enable();
            m_buttonSearch->enable();
            if(!m_searchstring.empty()) m_buttonReset->enable();
            return 1;
        }
        onSearchNext(NULL, 0, NULL);
        m_buttonIcase->enable();
        m_buttonFile->enable();
        m_buttonChannel->enable();
        m_buttonNetwork->enable();
        m_buttonAll->enable();
        m_file = TRUE;
        m_channel = FALSE;
        m_network = FALSE;
        m_all = FALSE;
    }
    else
    {
        m_text->clearText();
        scan();
        m_buttonIcase->enable();
        m_buttonFile->disable();
        m_file = FALSE;
        if(item->isChannel() || item->isMonth() || item->isYear())
        {
            m_buttonChannel->enable();
            m_channel = TRUE;
            m_network = FALSE;
            m_all = FALSE;
        }
        else
        {
            m_buttonChannel->disable();
            m_channel = FALSE;
            if(item->isNetwork())
            {
                m_buttonNetwork->enable();
                m_network = TRUE;
                m_all = FALSE;
            }
            else
            {
                m_buttonNetwork->disable();
                m_network = FALSE;
                m_all = TRUE;
            }
        }
        m_buttonAll->enable();
    }
    m_searchfield->enable();
    m_buttonSearch->enable();
    if(!m_searchstring.empty()) m_buttonReset->enable();
    return 1;
}

long LogViewer::onRightTree(FXObject*, FXSelector, void *ptr)
{
    FXEvent* event = (FXEvent*)ptr;
    if(event->moved) return 1;
    LogItem *item = (LogItem*)m_treeHistory->getItemAt(event->win_x,event->win_y);
    if(item)
    {
        if(item == m_treeHistory->getFirstItem())
            return 1;
        m_itemOnRight = item;
        FXMenuPane popup(this);
        if(!item->isLogFile() && item->getNumChildren())
        {
            if(item->isExpanded())
                new FXMenuCommand(&popup, _("Collapse"), NULL, this, LogViewer_PACK);
            else
                new FXMenuCommand(&popup, _("Expand"), NULL, this, LogViewer_UNPACK);
        }
        new FXMenuCommand(&popup, _("Delete"), NULL, this, LogViewer_DELETEITEM);
        popup.create();
        popup.popup(NULL,event->root_x,event->root_y);
        getApp()->runModalWhileShown(&popup);
    }
    return 1;
}

long LogViewer::onPack(FXObject*, FXSelector, void*)
{
    if(m_itemOnRight)
    {
        m_treeHistory->collapseTree(m_itemOnRight, TRUE);
    }
    return 1;
}

long LogViewer::onUnpack(FXObject*, FXSelector, void*)
{
    if(m_itemOnRight)
    {
        scan();
        m_treeHistory->expandTree(m_itemOnRight, TRUE);
    }
    return 1;
}

long LogViewer::onDelete(FXObject*, FXSelector, void*)
{
    if(m_itemOnRight)
    {
        if(m_itemOnRight == m_treeHistory->getFirstItem())
            return 1;
        FXString message;
        if(m_itemOnRight->isMonth() || m_itemOnRight->isYear() || m_itemOnRight->isChannel() || m_itemOnRight->isNetwork())
        {
            if(m_itemOnRight->hasItems())
                message = FXStringFormat(_("Delete %s with all child items?\nThis cann't be UNDONE!"), m_itemOnRight->getText().text());
            else
                message = FXStringFormat(_("Delete %s?\nThis cann't be UNDONE!"), m_itemOnRight->getText().text());
        }
        else if(m_itemOnRight->isLogFile())
            message = FXStringFormat(_("Delete file %s?\nThis cann't be UNDONE!"), getLogFileName(m_itemOnRight).text());
        else
            return 1;
        if(dxEXMessageBox::question(this, MBOX_YES_NO, _("Question"), message.text(), NULL) == 1)
        {
            if(m_itemOnRight->isYear() || m_itemOnRight->isMonth())
            {
                dxStringArray files = getLogFiles(m_itemOnRight);
                for(FXint i=0; i<files.no(); i++)
                    FXFile::remove(getItemPathname(m_itemOnRight)+PATHSEPSTRING+files[i]);
            }
            else
                FXFile::removeFiles(getItemPathname(m_itemOnRight), TRUE);
            scan();
        }
        else
            return 1;
    }
    return 1;
}

long LogViewer::onKeyPress(FXObject *sender, FXSelector sel, void *ptr)
{
    FXEvent *event = (FXEvent*)ptr;
    if(event->code == KEY_Escape)
    {
        handle(this, FXSEL(SEL_COMMAND, LogViewer_CLOSE), NULL);
        return 1;
    }
    else
    {
        if(FXTopWindow::onKeyPress(sender,sel,ptr))
            return 1;
    }
    return 0;
}

long LogViewer::onCmdSearchOptions(FXObject*, FXSelector sel, void*)
{
    switch(FXSELID(sel))
    {
    case LogViewer_ALL:
        m_all = TRUE;
        m_network = FALSE;
        m_channel = FALSE;
        m_file = FALSE;
        break;
    case LogViewer_NETWORK:
        m_all = FALSE;
        m_network = TRUE;
        m_channel = FALSE;
        m_file = FALSE;
        break;
    case LogViewer_CHANNEL:
        m_all = FALSE;
        m_network = FALSE;
        m_channel = TRUE;
        m_file = FALSE;
        break;
    case LogViewer_FILE:
        m_all = FALSE;
        m_network = FALSE;
        m_channel = FALSE;
        m_file = TRUE;
        break;
    }
    return 1;
}

long LogViewer::onTextLink(FXObject *, FXSelector, void *data)
{
    utils::launchLink(static_cast<FXchar*>(data));
    return 1;
}

long LogViewer::onTextRightMouse(FXObject *, FXSelector, void *ptr)
{
    FXEvent* event=(FXEvent*)ptr;
    if(event->moved) return 1;
    FXbool link = m_text->isXYLink(event->win_x, event->win_y);
    FXMenuPane popupmenu(this);
    if(link)
    {
        m_link = m_text->getLinkTextXY(event->win_x, event->win_y);
        new FXMenuCommand(&popupmenu, _("Open in a web browser"), NULL, this, LogViewer_OPENLINK);
        new FXMenuCommand(&popupmenu, _("Copy the link"), NULL, this, LogViewer_COPYLINK);
        new FXMenuSeparator(&popupmenu);
    }
    FXMenuCommand *selectall = new FXMenuCommand(&popupmenu, _("Select All"), ICO_SELECTALL, m_text, dxText::ID_SELECT_ALL);
    FXMenuCommand *copy = new FXMenuCommand(&popupmenu, _("Copy"), ICO_COPY, m_text, dxText::ID_COPY_SEL);
    new FXMenuSeparator(&popupmenu);
    FXMenuCommand *find = new FXMenuCommand(&popupmenu, _("Find"), ICO_FIND, m_text, dxText::ID_SEARCH);
    if(!m_text->hasSomeText())
    {
        selectall->disable();
        copy->disable();
        find->disable();
    }
    if(!m_text->hasSelectedText())
    {
        copy->disable();
    }
    popupmenu.create();
    popupmenu.popup(NULL,event->root_x,event->root_y);
    getApp()->runModalWhileShown(&popupmenu);
    return 1;
}

long LogViewer::onTextMiddleMouse(FXObject*, FXSelector, void *ptr)
{
    FXEvent* event=(FXEvent*)ptr;
    if(event->moved) return 1;
#ifndef WIN32 //middleclick copy
    m_text->copySelectedText();
#endif
    return 1;
}

long LogViewer::onOpenLink(FXObject*, FXSelector, void*)
{
    utils::launchLink(m_link);
    return 1;
}

long LogViewer::onCopyLink(FXObject*, FXSelector, void*)
{
    FXDragType types[4];
    types[0]=stringType;
    types[1]=textType;
    types[2]=utf8Type;
    types[3]=utf16Type;
    acquireClipboard(types,4);
    return 1;
}

// Somebody wants our selection
long LogViewer::onClipboardRequest(FXObject *sender, FXSelector sel, void *ptr)
{
    FXEvent *event = (FXEvent*)ptr;
    // Try handling it in base class first
    if(FXWindow::onClipboardRequest(sender, sel, ptr)) return 1;
    // Requested data from clipboard
    if(event->target==stringType || event->target==textType || event->target==utf8Type || event->target==utf16Type)
    {
        // Expand newlines to CRLF on Windows
#ifdef WIN32
        unixToDos(m_link);
#endif
        // Return clipped text as as UTF-8
        if(event->target==utf8Type)
        {
            FXTRACE((100, "Request UTF8\n"));
            setDNDData(FROM_CLIPBOARD, event->target, m_link);
            return 1;
        }
        // Return clipped text translated to 8859-1
        if(event->target==stringType || event->target==textType)
        {
            FX88591Codec ascii;
            FXTRACE((100, "Request ASCII\n"));
            setDNDData(FROM_CLIPBOARD, event->target, ascii.utf2mb(m_link));
            return 1;
        }
        // Return text of the selection translated to UTF-16
        if(event->target==utf16Type)
        {
            FXUTF16LECodec unicode;             // FIXME maybe other endianness for unix
            FXTRACE((100, "Request UTF16\n"));
            setDNDData(FROM_CLIPBOARD, event->target, unicode.utf2mb(m_link));
            return 1;
        }
    }
    return 0;
}

//Timeout for statuslabel clear fired
long LogViewer::onStatusTimeout(FXObject*, FXSelector, void*)
{
    m_statusbar->hide();
    m_statuslabel->setText(" ");
    return 1;
}

// Load file
FXbool LogViewer::loadFile(const FXString& file)
{
    clearLog();
    m_text->clearSelection();
    std::ifstream textfile(file.text());
    std::string buf;
    //Opened file?
    if(!textfile.is_open())
    {
        dxEXMessageBox::error(this,MBOX_OK,_("Error Loading File"),_("Unable to open file: %s"),file.text());
        return FALSE;
    }
    //Set wait cursor
    getApp()->beginWaitCursor();
    while(getline(textfile, buf))
    {
        appendLogLine(buf.c_str());
    }
    textfile.close();
    //Kill wait cursor
    getApp()->endWaitCursor();
    return TRUE;
}

void LogViewer::enableAllItems()
{
    if(!m_treeHistory->getFirstItem())
        return;
    LogItem *item = (LogItem*)m_treeHistory->getFirstItem();
    getApp()->beginWaitCursor();
    while(item)
    {
        m_treeHistory->enableItem(item);
        // Go deeper
        if(item->first)
        {
            item=(LogItem*)item->first;
            continue;
        }
        // Go up
        while(!item->next && item->parent)
        {
            item=(LogItem*)item->parent;
        }
        // Go to next
        item=(LogItem*)item->next;
    }
    getApp()->endWaitCursor();
}

FXString LogViewer::getItemPathname(const LogItem* item)
{
    FXString pathname;
    if(item)
    {
        if(item == m_treeHistory->getFirstItem())
            return m_logPath;
        while(1)
        {
            if(item!=m_treeHistory->getFirstItem() && !item->isMonth() && !item->isYear())
            {
                if(item->isLogFile())
                {
                    pathname.prepend(getLogFileName(item));
                }
                else pathname.prepend(item->getText());
            }
            item = (LogItem*)item->parent;
            if(!item) break;
            if(item->parent && !item->isMonth() && !item->isYear()) pathname.prepend(PATHSEP);
        }
        return pathname.prepend(m_logPath+PATHSEPSTRING);
    }
    return FXString::null;
}

void LogViewer::loadTree()
{
    LogItem *ritem = new LogItem(_("Logs"), NULL, NULL, NULL);
    ritem->setDraggable(FALSE);
    ritem->state = LogItem::ROOT;
    FXStat info;
    FXStat::statLink(m_logPath, info);
    ritem->date = info.modified();
    m_treeHistory->appendItem(NULL, ritem, TRUE);
    getApp()->beginWaitCursor();
    listChildItems(ritem);
    m_treeHistory->sortChildItems(ritem);
    m_treeHistory->expandTree(ritem, TRUE);
    getApp()->endWaitCursor();
}

void LogViewer::listChildItems(LogItem *par)
{
    if(par->isRoot())
        listRootChildItems(par);
    else if(par->isNetwork())
        listNetworkChildItems(par);
    else if(par->isChannel())
        listChannelChildItems(par);
    // Need to layout
    m_treeHistory->recalc();
}

//inspired by FXDirList
void LogViewer::scan()
{
    FXString pathname;
    LogItem *item;
    FXStat info;
    if(!m_treeHistory->getFirstItem())
        loadTree();
    item = (LogItem*)m_treeHistory->getFirstItem();
    getApp()->beginWaitCursor();
    while(item)
    {
        if(item->isRoot() || item->isNetwork() || item->isChannel())
        {
            // Get the full path of the item
            pathname=getItemPathname(item);
            // Stat this directory; should not fail as parent has been scanned already
            FXStat::statFile(pathname,info);
            // Get the mod date of the item
            FXTime newdate=info.touched();
            // date was changed
            if(item->date!=newdate || (!item->getNumChildren() && item->isExpanded()))
            {
                // And do the refresh
                listChildItems(item);
                // Remember when we did this
                item->date=newdate;
            }
            // Go deeper
            if(item->first)
            {
                item=(LogItem*)item->first;
                continue;
            }
        }
        // Go up
        while(!item->next && item->parent)
        {
            item=(LogItem*)item->parent;
        }
        // Go to next
        item=(LogItem*)item->next;
    }
    getApp()->endWaitCursor();
}

FXint LogViewer::getNickStyle(const FXString &nick)
{
    if(preferences.m_coloredNick) return 12+nick.hash()%8; //12 is first colored nick style
    return 5; //bold
}

void LogViewer::clearLog()
{
    dxHiliteStyle style = {preferences.m_colors.text,preferences.m_colors.back,getApp()->getSelforeColor(),getApp()->getSelbackColor(),0,FALSE};
    for(int i=0; i<19; i++)
    {
        m_textStyleList.append(style);
    }
    //gray text - user commands
    m_textStyleList[0].normalForeColor = preferences.m_colors.user;
    //orange text - Actions
    m_textStyleList[1].normalForeColor = preferences.m_colors.action;
    //blue text - Notice
    m_textStyleList[2].normalForeColor = preferences.m_colors.notice;
    //red text - Errors
    m_textStyleList[3].normalForeColor = preferences.m_colors.error;
    //bold style
    m_textStyleList[4].style = FXText::STYLE_BOLD;
    //underline style
    m_textStyleList[5].style = FXText::STYLE_UNDERLINE;
    //bold & underline
    m_textStyleList[6].style = FXText::STYLE_UNDERLINE;
    m_textStyleList[6].style ^=FXText::STYLE_BOLD;
    //highlight text
    m_textStyleList[7].normalForeColor = preferences.m_colors.hilight;
    //link style
    m_textStyleList[8].normalForeColor = preferences.m_colors.link;
    m_textStyleList[8].link = TRUE;
    //mymsg style
    m_textStyleList[9].normalForeColor = preferences.m_colors.mymsg;
    //log line style
    m_textStyleList[10].normalForeColor = preferences.m_colors.log;
    //next styles for colored nicks
    m_textStyleList[11].normalForeColor = FXRGB(196, 160, 0);
    m_textStyleList[12].normalForeColor = FXRGB(206, 92, 0);
    m_textStyleList[13].normalForeColor = FXRGB(143, 89, 2);
    m_textStyleList[14].normalForeColor = FXRGB(78, 154, 6);
    m_textStyleList[15].normalForeColor = FXRGB(32, 74, 135);
    m_textStyleList[16].normalForeColor = FXRGB(117, 80, 123);
    m_textStyleList[17].normalForeColor = FXRGB(164, 0, 0);
    m_textStyleList[18].normalForeColor = FXRGB(85, 87, 83);
    m_text->setHiliteStyles(m_textStyleList.data());
    m_text->setTextColor(preferences.m_colors.text);
    m_text->setBackColor(preferences.m_colors.back);
    m_text->clearText();
}

void LogViewer::appendLogLine(const FXString &line)
{
    if(line.length()>12 && line[11]=='\014') //log style mark
    {
        FXint style = FXIntVal(line.after('\014').before('\014'));
        m_text->appendText(line.before('\014'));
        if(line.after('\014',2)[0]=='<') //has nick
        {
            FXString nick = line.after('<').before('>');
            FXString msg = line.after('>');
            appendLinkText(nick+":"+msg, style);
        }
        else appendLinkText(line.after('\014', 2), style);
    }
    else
    {
        if(line.length()>12 && line[11]=='<') //line with nick
        {
            m_text->appendText(line.before('<'));
            FXString nick = line.after('<').before('>');
            FXString msg = line.after('>').trimBegin();
            m_text->appendStyledText(nick+": ", getNickStyle(nick));
            if(preferences.m_stripColors) msg = utils::stripColors(msg, FALSE);
            appendLinkText(msg, 0);
        }
        else appendLinkText(line, 0);
    }
}

void LogViewer::appendLinkText(const FXString &txt, FXint stylenum)
{
    FXint i = 0;
    FXint linkLength = 0;
    FXbool bold = FALSE;
    FXbool under = FALSE;
    FXint lastStyle = stylenum;
    FXColor foreColor = stylenum && stylenum<=m_textStyleList.no() ? m_textStyleList[stylenum-1].normalForeColor : preferences.m_colors.text;
    FXColor backColor = stylenum && stylenum<=m_textStyleList.no() ? m_textStyleList[stylenum-1].normalBackColor : preferences.m_colors.back;
    FXString normalText = "";
    FXint length = txt.length();
    while(i<length)
    {
        if(txt[i]=='h' && !comparecase(txt.mid(i,7),"http://"))
        {
            if(!normalText.empty())
            {
                m_text->appendStyledText(normalText, lastStyle);
                normalText.clear();
            }
            for(FXint j=i; j<length; j++)
            {
                if(isBadchar(txt[j]))
                {
                    break;
                }
                linkLength++;
            }
            m_text->appendStyledText(txt.mid(i, linkLength), linkLength>7 ? 9 : stylenum);
            i+=linkLength-1;
            linkLength=0;
        }
        else if(txt[i]=='h' && !comparecase(txt.mid(i,8),"https://"))
        {
            if(!normalText.empty())
            {
                m_text->appendStyledText(normalText, lastStyle);
                normalText.clear();
            }
            for(FXint j=i; j<length; j++)
            {
                if(isBadchar(txt[j]))
                {
                    break;
                }
                linkLength++;
            }
            m_text->appendStyledText(txt.mid(i, linkLength), linkLength>8 ? 9 : stylenum);
            i+=linkLength-1;
            linkLength=0;
        }
        else if(txt[i]=='f' && !comparecase(txt.mid(i,6),"ftp://"))
        {
            if(!normalText.empty())
            {
                m_text->appendStyledText(normalText, lastStyle);
                normalText.clear();
            }
            for(FXint j=i; j<length; j++)
            {
                if(isBadchar(txt[j]))
                {
                    break;
                }
                linkLength++;
            }
            m_text->appendStyledText(txt.mid(i, linkLength), linkLength>6 ? 9 : stylenum);
            i+=linkLength-1;
            linkLength=0;
        }
        else if(txt[i]=='w' && !comparecase(txt.mid(i,4),"www."))
        {
            if(!normalText.empty())
            {
                m_text->appendStyledText(normalText, lastStyle);
                normalText.clear();
            }
            for(FXint j=i; j<length; j++)
            {
                if(isBadchar(txt[j]))
                {
                    break;
                }
                linkLength++;
            }
            m_text->appendStyledText(txt.mid(i, linkLength), linkLength>4 ? 9 : stylenum);
            i+=linkLength-1;
            linkLength=0;
        }
        else
        {
            if(txt[i] == '\002') //bold
            {
                if(!normalText.empty())
                {
                    m_text->appendStyledText(normalText, lastStyle);
                    normalText.clear();
                }
                bold = !bold;
                FXuint style = 0;
                if(bold && under) style = FXText::STYLE_BOLD|FXText::STYLE_UNDERLINE;
                else if(bold && !under) style = FXText::STYLE_BOLD;
                else if(!bold && under) style = FXText::STYLE_UNDERLINE;
                lastStyle = hiliteStyleExist(foreColor, backColor, style);
                if(lastStyle == -1)
                {
                    //dxText has available max. 255 styles
                    if(m_textStyleList.no()<256)
                    {
                        createHiliteStyle(foreColor, backColor, style);
                        lastStyle = m_textStyleList.no();
                    }
                    else lastStyle = 0;
                }
            }
            else if(txt[i] == '\026') //reverse
            {
                if(!normalText.empty())
                {
                    m_text->appendStyledText(normalText, lastStyle);
                    normalText.clear();
                }
                FXuint style = 0;
                if(bold && under) style = FXText::STYLE_BOLD|FXText::STYLE_UNDERLINE;
                else if(bold && !under) style = FXText::STYLE_BOLD;
                else if(!bold && under) style = FXText::STYLE_UNDERLINE;
                FXColor tempColor = foreColor;
                foreColor = backColor;
                backColor = tempColor;
                lastStyle = hiliteStyleExist(foreColor, backColor, style);
                if(lastStyle == -1)
                {
                    //dxText has available max. 255 styles
                    if(m_textStyleList.no()<256)
                    {
                        createHiliteStyle(foreColor, backColor, style);
                        lastStyle = m_textStyleList.no();
                    }
                    else lastStyle = 0;
                }
            }
            else if(txt[i] == '\037') //underline
            {
                if(!normalText.empty())
                {
                    m_text->appendStyledText(normalText, lastStyle);
                    normalText.clear();
                }
                under = !under;
                FXuint style = 0;
                if(bold && under) style = FXText::STYLE_BOLD|FXText::STYLE_UNDERLINE;
                else if(bold && !under) style = FXText::STYLE_BOLD;
                else if(!bold && under) style = FXText::STYLE_UNDERLINE;
                lastStyle = hiliteStyleExist(foreColor, backColor, style);
                if(lastStyle == -1)
                {
                    //dxText has available max. 255 styles
                    if(m_textStyleList.no()<256)
                    {
                        createHiliteStyle(foreColor, backColor, style);
                        lastStyle = m_textStyleList.no();
                    }
                    else lastStyle = 0;
                }
            }
            else if(txt[i] == '\021') //fixed
            {
                utils::debugLine("Poslan fixed styl");
            }
            else if(txt[i] == '\035') //italic
            {
                utils::debugLine("Poslan italic styl");
            }
            else if(txt[i] == '\003') //color
            {
                if(!normalText.empty())
                {
                    m_text->appendStyledText(normalText, lastStyle);
                    normalText.clear();
                }
                FXuint style=0;
                if(bold && under) style = FXText::STYLE_BOLD|FXText::STYLE_UNDERLINE;
                else if(bold && !under) style = FXText::STYLE_BOLD;
                else if(!bold && under) style = FXText::STYLE_UNDERLINE;
                FXbool isHexColor = FALSE;
                FXint colorLength = 0;
                foreColor = preferences.m_colors.text;
                backColor = preferences.m_colors.back;
                if(i+1<length)
                {
                    if(txt[i+1] == '#') isHexColor = TRUE;
                }
                if(isHexColor)
                {
                    if(FXRex("^\\h\\h\\h\\h\\h\\h+$").match(txt.mid(i+2,6)))
                    {
                        foreColor = FXRGB(FXIntVal(txt.mid(i+2,2),16),FXIntVal(txt.mid(i+4,2),16),FXIntVal(txt.mid(i+6,2),16));
                        colorLength +=7;
                    }
                    if(i+8 < length && txt[i+8] == ',' && FXRex("^\\h\\h\\h\\h\\h\\h+$").match(txt.mid(i+10,6)))
                    {
                        backColor = FXRGB(FXIntVal(txt.mid(i+10,2),16),FXIntVal(txt.mid(i+12,2),16),FXIntVal(txt.mid(i+14,2),16));
                        colorLength +=8;
                    }
                }
                else
                {
                    if(i+2<length)
                    {
                        FXint code = -1;
                        if(isdigit(txt[i+1]))
                        {
                            if(isdigit(txt[i+2]))
                            {
                                code = (txt[i+1]-48)*10+txt[i+2]-48;
                                colorLength +=2;
                            }
                            else
                            {
                                code = txt[i+1]-48;
                                colorLength ++;
                            }
                        }
                        if(code!=-1)
                            foreColor = utils::getIrcColor(code%16);
                    }
                    if(i+colorLength+1 < length && txt[i+colorLength+1] == ',')
                    {
                        FXint code = -1;
                        if(isdigit(txt[i+colorLength+2]))
                        {
                            if(isdigit(txt[i+colorLength+3]))
                            {
                                code = (txt[i+colorLength+2]-48)*10+txt[i+colorLength+3]-48;
                                colorLength +=3;
                            }
                            else
                            {
                                code = txt[i+colorLength+2]-48;
                                colorLength +=2;
                            }
                        }
                        if(code!=-1)
                            backColor = utils::getIrcColor(code%16);
                    }
                }
                lastStyle = hiliteStyleExist(foreColor, backColor, style);
                if(lastStyle == -1)
                {
                    //dxText has available max. 255 styles
                    if(m_textStyleList.no()<256)
                    {
                        createHiliteStyle(foreColor, backColor, style);
                        lastStyle = m_textStyleList.no();
                    }
                    else lastStyle = 0;
                };
                i +=colorLength;
            }
            else if(txt[i] == '\017') //reset
            {
                if(!normalText.empty())
                {
                    m_text->appendStyledText(normalText, lastStyle);
                    normalText.clear();
                }
                bold = FALSE;
                under = FALSE;
                foreColor = preferences.m_colors.text;
                backColor = preferences.m_colors.back;
                lastStyle = stylenum;
            }
            else
            {
                normalText.append(txt[i]);
            }
        }
        i++;
    }
    if(!normalText.empty())
    {
        m_text->appendStyledText(normalText, lastStyle);
    }
    m_text->appendText("\n");
}

FXint LogViewer::hiliteStyleExist(FXColor foreColor, FXColor backColor, FXuint style)
{
    for(FXint i=0; i<m_textStyleList.no(); i++)
    {
        if(m_textStyleList[i].normalForeColor == foreColor
                && m_textStyleList[i].normalBackColor == backColor
                && m_textStyleList[i].style == style)
            return i+1;
    }
    return -1;
}

void LogViewer::createHiliteStyle(FXColor foreColor, FXColor backColor, FXuint style)
{
    dxHiliteStyle nstyle = {foreColor,backColor,getApp()->getSelforeColor(),getApp()->getSelbackColor(),style,FALSE};
    m_textStyleList.append(nstyle);
    m_text->setHiliteStyles(m_textStyleList.data());
}

LogItem *LogViewer::getChannelItem(LogItem* item)
{
    if(item->isLogFile())
    {
        item = (LogItem*)item->parent; //Month item
        item = (LogItem*)item->parent; //Year item
        return (LogItem*)item->parent;
    }
    else if(item->isMonth())
    {
        item = (LogItem*)item->parent; //Year item
        return (LogItem*)item->parent;
    }
    else if(item->isYear())
    {
        return (LogItem*)item->parent;
    }
    else if(item->isChannel())
    {
        return item;
    }
    else return NULL;
}

LogItem *LogViewer::getNetworkItem(LogItem* item)
{
    if(item->isLogFile())
    {
        item = (LogItem*)item->parent; //Month item
        item = (LogItem*)item->parent; //Year item
        item = (LogItem*)item->parent; //Channel/Query item
        return (LogItem*)item->parent;
    }
    else if(item->isMonth())
    {
        item = (LogItem*)item->parent; //Year item
        item = (LogItem*)item->parent; //Channel/Query item
        return (LogItem*)item->parent;
    }
    else if(item->isYear())
    {
        item = (LogItem*)item->parent; //Channel/Query item
        return (LogItem*)item->parent;
    }
    else if(item->isChannel())
    {
        return (LogItem*)item->parent;
    }
    else if(item->isNetwork())
    {
        return item;
    }
    else return NULL;
}

void LogViewer::searchInLogFile()
{
    FXint index[10];
    FXint beg[10];
    FXint end[10];
    if(!m_searchstring.empty())
    {
        FXuint flags = SEARCH_FORWARD|SEARCH_WRAP;
        if(m_icase) flags |= SEARCH_IGNORECASE;
        if(m_text->findText(m_searchstring,index,beg,end,0,0,flags|SEARCH_WRAP,10))
        {
            m_text->setSelection(index[0], beg[0], index[0], end[0]-1);
            m_text->makePositionVisible(index[0], end[0]);
            m_buttonReset->enable();
        }
        else
        {
            getApp()->beep();
            dxEXMessageBox::information(this, MBOX_OK, _("Information"), _("'%s' wasn't found"), m_searchstring.text());
            m_searchstring = "";
        }
    }
}

FXint LogViewer::searchInChannel(LogItem* item, FXbool showMessage)
{
    if(!item) return 0;
    if(!item->isChannel()) return 0;
    FXint count = 0;
    FXRex rex;
    FXint rexmode = REX_VERBATIM;
    if(m_icase) rexmode |= REX_ICASE;
    if(!m_searchstring.empty())
    {
        m_text->clearText();
        if(rex.parse(m_searchstring, rexmode) == REGERR_OK)
        {
            getApp()->beginWaitCursor();
            if(!item->getNumChildren())
                listChildItems(item);
            LogItem *year = (LogItem*)item->first; //item is year
            while(year)
            {
                FXint mhidden = 0;
                LogItem *month = (LogItem*)year->first;
                while(month)
                {
                    FXint fhidden = 0;
                    LogItem *fitem = (LogItem*)month->first;
                    while(fitem)
                    {
                        std::ifstream stream(getItemPathname(fitem).text());
                        std::string temp;
                        bool contain = FALSE;
                        while(std::getline(stream, temp))
                        {
                            if(rex.match(temp.c_str()))
                            {
                                contain = TRUE;
                                count++;
                                break;
                            }
                        }
                        if(contain) m_treeHistory->enableItem(fitem);
                        else
                        {
                            m_treeHistory->disableItem(fitem);
                            fhidden++;
                        }
                        fitem = (LogItem*)fitem->next;
                    }
                    if(month->getNumChildren() == fhidden)
                    {
                        m_treeHistory->disableItem(month);
                        mhidden++;
                    }
                    month = (LogItem*)month->next;
                }
                if(year->getNumChildren() == mhidden)
                    m_treeHistory->disableItem(year);
                year = (LogItem*)year->next;
            }
            getApp()->endWaitCursor();
            if(count == 0)
            {
                if(showMessage)
                {
                    dxEXMessageBox::information(this, MBOX_OK, _("Information"), _("'%s' wasn't found"), m_searchstring.text());
                    m_searchstring = "";
                    item = (LogItem*)item->first;
                    while(item)
                    {
                        if(item->isChannel())
                            break;
                        m_treeHistory->enableItem(item);
                        item = (LogItem*)item->getBelow();
                    }
                }
            }
            else
                m_buttonReset->enable();
        }
    }
    return count;
}

FXint LogViewer::searchInNetwork(LogItem* item, FXbool showMessage)
{
    if(!item) return 0;
    if(!item->isNetwork()) return 0;
    FXint count = 0;
    getApp()->beginWaitCursor();
    if(!item->getNumChildren())
        listChildItems(item);
    LogItem *channel = (LogItem*)item->first; //Channel/Query item;
    while(channel)
    {
        count += searchInChannel(channel, FALSE);
        FXint yhidden = 0;
        //disable channel item?
        LogItem *year = (LogItem*)channel->first; //item is year
        while(year)
        {
            if(!year->isEnabled()) yhidden++;
            year = (LogItem*)year->next;
        }
        if(channel->getNumChildren() == yhidden)
            m_treeHistory->disableItem(channel);
        channel = (LogItem*)channel->next;
    }
    getApp()->endWaitCursor();
    if(count == 0)
    {
        if(showMessage)
        {
            dxEXMessageBox::information(this, MBOX_OK, _("Information"), _("'%s' wasn't found"), m_searchstring.text());
            m_searchstring = "";
            enableAllItems();
        }
    }
    else
        m_buttonReset->enable();
    return count;
}

void LogViewer::searchInAll()
{
    //we need load full tree
    if(!m_treeLoaded)
    {
        getApp()->beginWaitCursor();
        LogItem *item = (LogItem*)m_treeHistory->getFirstItem();
        while(item!=NULL)
        {
            listChildItems(item);
            item=(LogItem*)item->getBelow();
        }
        getApp()->endWaitCursor();
        m_treeLoaded = TRUE;
    }
    FXint count = 0;
    getApp()->beginWaitCursor();
    LogItem *item = (LogItem*)m_treeHistory->getFirstItem();
    item = (LogItem*)item->first; //Network item
    while(item)
    {
        count += searchInNetwork(item, FALSE);
        FXint chhidden = 0;
        //disable nework item?
        LogItem *channel = (LogItem*)item->first;
        while(channel)
        {
            if(!channel->isEnabled()) chhidden++;
            channel = (LogItem*)channel->next;
        }
        if(item->getNumChildren() == chhidden)
            m_treeHistory->disableItem(item);
        item = (LogItem*)item->next;
    }
    getApp()->endWaitCursor();
    if(count == 0)
    {
        m_searchstring = "";
        dxEXMessageBox::information(this, MBOX_OK, _("Information"), _("'%s' wasn't found"), m_searchstring.text());
        enableAllItems();
    }
    else
    {
        LogItem *item = (LogItem*)m_treeHistory->getFirstItem()->getFirst();
        FXint disabled = 0;
        while(item)
        {
            LogItem *chitem = (LogItem*)item->first;
            while(chitem)
            {
                if(!chitem->isEnabled()) disabled++;
                chitem = (LogItem*)chitem->next;
            }
            if(item->getNumChildren()==disabled) m_treeHistory->disableItem(item);
            disabled = 0;
            item = (LogItem*)item->next;
        }
        m_buttonReset->enable();
    }
}

void LogViewer::listChannelChildItems(LogItem* channel)
{
    FXString directory = getItemPathname(channel);
    if(!FXStat::exists(directory))
        return;
    LogItem *year, *month, *day;
    FXDir dir;
    FXString path, name;
    FXStat info;
    dxStringArray oldfiles, files;
    oldfiles = getLogFiles(channel);
    // Managed to open directory
    if(dir.open(directory))
    {
        // Process directory entries
        while(dir.next())
        {
            // Get name of entry
            name = dir.name();
            // A dot special file?
            if(name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) continue;
            // Hidden file or directory normally not shown
            if(name[0] == '.') continue;
            // Build full pathname of entry
            path = directory;
            if (!ISPATHSEP(path[path.length() - 1])) path += PATHSEPSTRING;
            path += name;
#ifndef WIN32
            // Get file/link info
            if(!FXStat::statLink(path, info)) continue;
            // If its a link, it's not dxirc logs
            if(info.isLink()) continue;
#else
            // Get file/link info
            if(!FXStat::statFile(path, info)) continue;
            // Hidden file or directory normally not shown
            if(info.isHidden()) continue;
#endif
            // If it is directory, don't need channel log haven't subfolders
            if(info.isDirectory()) continue;
            // If it is not a directory, and not showing files and matching pattern skip it
            if(!info.isDirectory() && !utils::isRightLogFile(path, name)) continue;
            else
            {
                // Find it, and take it out from the old list if found
                bool found = false;
                for(FXint i=0; i<oldfiles.no(); i++)
                {
                    if(compare(oldfiles[i], name)==0)
                    {
                        found = true;
                        oldfiles.erase(i);
                        break;
                    }
                }
                if(!found) files.append(name);
            }
        }
        // Close it
        dir.close();
    }
    // Wipe items remaining in oldlist
    for(FXint i=0; i<oldfiles.no(); i++)
    {
        LogItem *item = (LogItem*)channel->first;
        while(item)
        {
            if(item->isChannel() || item->isNetwork())
                break;
            if(item->isLogFile())
            {
                if(getLogFileName(item)==oldfiles[i])
                {
                    day = item;
                    month = (LogItem*)day->parent;
                    year = (LogItem*)month->parent;
                    m_treeHistory->removeItem(day);
                    if(!month->getNumChildren())
                        m_treeHistory->removeItem(month);
                    if(!year->getNumChildren())
                        m_treeHistory->removeItem(year);
                    break;
                }
            }
            // Go to next
            item=(LogItem*)item->getBelow();
        }
    }
    if(!files.no())
        return;
    utils::sortStringArray(files);
    FXString lastYear = utils::getParam(files[0], 1, FALSE, '-');
    FXint lastMonth = FXIntVal(utils::getParam(files[0], 2, FALSE, '-'));
    year = getItem(channel, lastYear);
    if(!year)
    {
        year = new LogItem(lastYear, ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
        year->state |= LogItem::YEAR;
        m_treeHistory->appendItem(channel, year, TRUE);
    }
    month = getItem(year, utils::getMonth(lastMonth));
    if(!month)
    {
        month = new LogItem(utils::getMonth(lastMonth), ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
        month->state |= LogItem::MONTH;
        m_treeHistory->appendItem(year, month, TRUE);
    }
    for(FXint i=0; i<files.no(); i++)
    {
        if(lastYear!=utils::getParam(files[i], 1, FALSE, '-'))
        {
            m_treeHistory->sortChildItems(year);
            lastYear = utils::getParam(files[i], 1, FALSE, '-');
            lastMonth = FXIntVal(utils::getParam(files[i], 2, FALSE, '-'));
            year = getItem(channel, lastYear);
            if(!year)
            {
                year = new LogItem(lastYear, ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
                year->state |= LogItem::YEAR;
                m_treeHistory->appendItem(channel, year, TRUE);
            }
            month = getItem(year, utils::getMonth(lastMonth));
            if(!month)
            {
                month = new LogItem(utils::getMonth(lastMonth), ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
                month->state |= LogItem::MONTH;
                m_treeHistory->appendItem(year, month, TRUE);
            }
            day = new LogItem(utils::getParam(files[i], 3, FALSE, '-'), ICO_FILE, ICO_FILE, NULL);
            day->state |= LogItem::LOGFILE;
            day->state &= ~LogItem::HASITEMS;
            m_treeHistory->appendItem(month, day, TRUE);
        }
        else if(lastMonth!=FXIntVal(utils::getParam(files[i], 2, FALSE, '-')))
        {
            m_treeHistory->sortChildItems(month);
            lastMonth = FXIntVal(utils::getParam(files[i], 2, FALSE, '-'));
            month = getItem(year, utils::getMonth(lastMonth));
            if(!month)
            {
                month = new LogItem(utils::getMonth(lastMonth), ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
                month->state |= LogItem::MONTH;
                m_treeHistory->appendItem(year, month, TRUE);
            }
            day = new LogItem(utils::getParam(files[i], 3, FALSE, '-'), ICO_FILE, ICO_FILE, NULL);
            day->state |= LogItem::LOGFILE;
            day->state &= ~LogItem::HASITEMS;
            m_treeHistory->appendItem(month, day, TRUE);
        }
        else
        {
            day = new LogItem(utils::getParam(files[i], 3, FALSE, '-'), ICO_FILE, ICO_FILE, NULL);
            day->state |= LogItem::LOGFILE;
            day->state &= ~LogItem::HASITEMS;
            m_treeHistory->appendItem(month, day, TRUE);
        }
    }
    // Now we know for sure whether we really have subitems or not
    if(channel->first)
        channel->state |= LogItem::HASITEMS;
    else
        channel->state &= ~LogItem::HASITEMS;
    m_treeHistory->sortChildItems(month);
    m_treeHistory->sortChildItems(year);
    m_treeHistory->sortChildItems(channel);
}

void LogViewer::listNetworkChildItems(LogItem* network)
{
    FXString directory = getItemPathname(network);
    if(!FXStat::exists(directory))
        return;
    dxStringArray oldchannels;
    LogItem *channel;
    channel = (LogItem*)network->first;
    while(channel)
    {
        oldchannels.append(channel->getText());
        // Go to next
        channel=(LogItem*)channel->next;
    }
    FXDir dir;
    FXString path, name;
    FXStat info;
    // Managed to open directory
    if(dir.open(directory))
    {
        // Process directory entries
        while(dir.next())
        {
            // Get name of entry
            name = dir.name();
            // A dot special file?
            if(name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) continue;
            // Hidden file or directory normally not shown
            if(name[0] == '.') continue;
            // Build full pathname of entry
            path = directory;
            if (!ISPATHSEP(path[path.length() - 1])) path += PATHSEPSTRING;
            path += name;
#ifndef WIN32
            // Get file/link info
            if(!FXStat::statLink(path, info)) continue;
            // If its a link, it's not dxirc logs
            if(info.isLink()) continue;
#else
            // Get file/link info
            if(!FXStat::statFile(path, info)) continue;
            // Hidden file or directory normally not shown
            if(info.isHidden()) continue;
#endif
            // If it is directory, right for network
            if(info.isDirectory())
            {
                bool found = false;
                for(FXint i=0; i<oldchannels.no(); i++)
                {
                    if(oldchannels[i]==name)
                    {
                        found = true;
                        oldchannels.erase(i);
                        break;
                    }
                }
                if(!found)
                {
                    channel = new LogItem(name, ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
                    channel->state |= LogItem::CHANNEL;
                    channel->state |= LogItem::HASITEMS;
                    channel->date = info.modified();
                    m_treeHistory->appendItem(network, channel, TRUE);
                }
            }
            else continue;
        }
        // Close it
        dir.close();
    }
    // Wipe items remaining in oldlist
    for(FXint i=0; i<oldchannels.no(); i++)
    {
        LogItem *item = (LogItem*)network->first;
        while(item)
        {
            if(item->getText()==oldchannels[i])
            {
                m_treeHistory->removeItem(item);
                break;
            }
            // Go to next
            item=(LogItem*)item->next;
        }
    }
    // Now we know for sure whether we really have subitems or not
    if(network->first)
        network->state |= LogItem::HASITEMS;
    else
        network->state &= ~LogItem::HASITEMS;
    m_treeHistory->sortChildItems(network);
}

void LogViewer::listRootChildItems(LogItem* root)
{
    FXString directory = getItemPathname(root);
    if(!FXStat::exists(directory))
    {
        m_treeHistory->clearItems();
        return;
    }
    dxStringArray oldnetworks;
    LogItem *network;
    network = (LogItem*)root->first;
    while(network)
    {
        oldnetworks.append(network->getText());
        // Go to next
        network=(LogItem*)network->next;
    }
    FXDir dir;
    FXString path, name;
    FXStat info;
    // Managed to open directory
    if(dir.open(directory))
    {
        // Process directory entries
        while(dir.next())
        {
            // Get name of entry
            name = dir.name();
            // A dot special file?
            if(name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) continue;
            // Hidden file or directory normally not shown
            if(name[0] == '.') continue;
            // Build full pathname of entry
            path = directory;
            if (!ISPATHSEP(path[path.length() - 1])) path += PATHSEPSTRING;
            path += name;
#ifndef WIN32
            // Get file/link info
            if(!FXStat::statLink(path, info)) continue;
            // If its a link, it's not dxirc logs
            if(info.isLink()) continue;
#else
            // Get file/link info
            if(!FXStat::statFile(path, info)) continue;
            // Hidden file or directory normally not shown
            if(info.isHidden()) continue;
#endif
            // If it is directory, right for network
            if(info.isDirectory())
            {
                bool found = false;
                for(FXint i=0; i<oldnetworks.no(); i++)
                {
                    if(oldnetworks[i]==name)
                    {
                        found = true;
                        oldnetworks.erase(i);
                        break;
                    }
                }
                if(!found)
                {
                    network = new LogItem(name, ICO_OPENFOLDER, ICO_CLOSEFOLDER, NULL);
                    network->state |= LogItem::NETWORK;
                    network->state |= LogItem::HASITEMS;
                    network->date = info.modified();
                    m_treeHistory->appendItem(root, network, TRUE);
                }
            }
            else continue;
        }
        // Close it
        dir.close();
    }
    // Wipe items remaining in oldlist
    for(FXint i=0; i<oldnetworks.no(); i++)
    {
        LogItem *item = (LogItem*)root->first;
        while(item)
        {
            if(item->getText()==oldnetworks[i])
            {
                m_treeHistory->removeItem(item);
                break;
            }
            // Go to next
            item=(LogItem*)item->next;
        }
    }
    // Now we know for sure whether we really have subitems or not
    if(root->first)
        root->state |= LogItem::HASITEMS;
    else
        root->state &= ~LogItem::HASITEMS;
    m_treeHistory->sortChildItems(root);
}

//get child of parent with text only first sublevel
LogItem* LogViewer::getItem(LogItem *parent, FXString text)
{
    register LogItem *item;
    item = (LogItem*)parent->getFirst();
    while(item!=NULL && item->getParent()==parent)
    {
        if(item->getText()==text) return item;
        item=(LogItem*)item->getNext();
    }
    return NULL;
}

dxStringArray LogViewer::getLogFiles(LogItem* parent)
{
    dxStringArray array;
    array.no(0);
    register LogItem *item;
    item = (LogItem*)parent->first;
    while(item)
    {
        if(parent->isChannel() && (item->isChannel() || item->isNetwork()))
            break;
        else if(parent->isYear() && item->isYear())
            break;
        else if(parent->isMonth() && item->isMonth())
            break;
        if(item->isLogFile())
        {
            array.append(getLogFileName(item));
        }
        // Go to next
        item=(LogItem*)item->getBelow();
    }
    return array;
}

void LogViewer::updateStatus(const FXString& text)
{
    m_statusbar->show();
    m_statuslabel->setText(text);
    getApp()->addTimeout(this, LogViewer_STIMEOUT, 5000);
}

FXString LogViewer::getLogFileName(const LogItem* item)
{
    if(item->isLogFile())
    {
        FXString name = item->getParent()->getParent()->getText(); //year item
        name.append('-');
        name.append(utils::getMonthNumber(item->getParent()->getText())); //month item
        name.append('-');
        name.append(item->getText());
        return name;
    }
    return "";
}

